package ntrees.hu.renren;

import java.util.ArrayList;
import java.util.Arrays;

import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncResult;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;

import com.cooliris.picasa.Entry;
import com.cooliris.picasa.EntrySchema;
import com.cooliris.picasa.TableContentProvider;

public class RenrenContentProvider extends TableContentProvider {
    public static final String AUTHORITY = "ntrees.hu.renren.contentprovider";
    public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
    public static final Uri PHOTOS_URI = Uri.withAppendedPath(BASE_URI, "photos");
    public static final Uri ALBUMS_URI = Uri.withAppendedPath(BASE_URI, "albums");

    private static final String TAG = "===============> RenrenContentProvider";
    private static final String[] ALBUM_ID_EDITED_PROJECTION = { "_id", "upload_time" };
    private static final String[] ID_EDITED_INDEX_PROJECTION = { "_id"/*, "upload_time", "display_index"*/ };
    private static final String WHERE_ACCOUNT = "uid=?"; //FIXME:sync_account
    private static final String WHERE_ALBUM_ID = "aid=?";

    private final RenrenPhotoEntry mPhotoInstance = new RenrenPhotoEntry();
    private final RenrenAlbumEntry mAlbumInstance = new RenrenAlbumEntry();
    private SyncContext mSyncContext = null;
    //private Account mActiveAccount;
	
    @Override
    public void attachInfo(Context context, ProviderInfo info) {
    	Log.d(TAG, "attachInfo");
        // Initialize the provider and set the database.
        super.attachInfo(context, info);
        setDatabase(new Database(context, Database.DATABASE_NAME));

        // Add mappings for each of the exposed tables.
        addMapping(AUTHORITY, "photos", "vnd.ntrees.hu.renren.photo", RenrenPhotoEntry.SCHEMA);
        addMapping(AUTHORITY, "albums", "vnd.ntrees.hu.renren.album", RenrenAlbumEntry.SCHEMA);

        // Create the sync context.
        try {
            mSyncContext = new SyncContext(context);
        } catch (Exception e) {
            // The database wasn't created successfully, we create a memory backed database.
            setDatabase(new Database(context, null));
        }
    }
    
    
    
    public static final class Database extends SQLiteOpenHelper {
        public static final String DATABASE_NAME = "renren.db";
        public static final int DATABASE_VERSION = 83;

        public Database(Context context, String name) {
            super(context, name, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            RenrenPhotoEntry.SCHEMA.createTables(db);
            RenrenAlbumEntry.SCHEMA.createTables(db);
            RenrenUserEntry.SCHEMA.createTables(db);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // No new versions yet, if we are asked to upgrade we just reset
            // everything.
            RenrenPhotoEntry.SCHEMA.dropTables(db);
            RenrenAlbumEntry.SCHEMA.dropTables(db);
            RenrenUserEntry.SCHEMA.dropTables(db);
            onCreate(db);
        }
    }
    
    
    public void syncUsers(SyncResult syncResult) {
        syncUsers(mSyncContext, syncResult);
    }
    
    private RenrenUserEntry[] syncUsers(SyncContext context, SyncResult syncResult) {
    	return null;
        // Get authorized accounts.
        //context.reloadAccounts();
        //PicasaApi.AuthAccount[] accounts = context.accounts;
//        int numUsers = accounts.length;
//        UserEntry[] users = new UserEntry[numUsers];
//
//        // Scan existing accounts.
//        EntrySchema schema = UserEntry.SCHEMA;
//        SQLiteDatabase db = context.db;
//        Cursor cursor = schema.queryAll(db);
//        if (cursor.moveToFirst()) {
//            do {
//                // Read the current account.
//                UserEntry entry = new UserEntry();
//                schema.cursorToObject(cursor, entry);
//
//                // Find the corresponding account, or delete the row if it does
//                // not exist.
//                int i;
//                for (i = 0; i != numUsers; ++i) {
//                    if (accounts[i].user.equals(entry.account)) {
//                        users[i] = entry;
//                        break;
//                    }
//                }
//                if (i == numUsers) {
//                    Log.e(TAG, "Deleting user " + entry.account);
//                    entry.albumsEtag = null;
//                    deleteUser(db, entry.account);
//                }
//            } while (cursor.moveToNext());
//        } else {
//            // Log.i(TAG, "No users in database yet");
//        }
//        cursor.close();
//
//        // Add new accounts and synchronize user albums if recursive.
//        for (int i = 0; i != numUsers; ++i) {
//            UserEntry entry = users[i];
//            PicasaApi.AuthAccount account = accounts[i];
//            if (entry == null) {
//                entry = new UserEntry();
//                entry.account = account.user;
//                users[i] = entry;
//                Log.e(TAG, "Inserting user " + entry.account);
//            }
//        }
//        return users;
    }
    
    
    public void syncUsersAndAlbums(final boolean syncAlbumPhotos, SyncResult syncResult) {
        Log.d(TAG, "syncUsersAndAlbums");

        
    	SyncContext context = mSyncContext;

        // Synchronize users authenticated on the device.
        //UserEntry[] users = syncUsers(context, syncResult);

        // Synchronize albums for each user.
        String activeUsername = null;
//        if (mActiveAccount != null) {
//            activeUsername = PicasaApi.canonicalizeUsername(mActiveAccount.name);
//        }
        
//        boolean didSyncActiveUserName = false;
//        for (int i = 0, numUsers = users.length; i != numUsers; ++i) {
//            if (activeUsername != null && !context.accounts[i].user.equals(activeUsername))
//                continue;
//            if (!ContentResolver.getSyncAutomatically(context.accounts[i].account, AUTHORITY))
//                continue;
//            didSyncActiveUserName = true;
//            context.api.setAuth(context.accounts[i]);
//            syncUserAlbums(context, users[i], syncResult);
//            if (syncAlbumPhotos) {
//                syncUserPhotos(context, users[i].account, syncResult);
//            } else {
//                // // Always sync added albums.
//                // for (Long albumId : context.albumsAdded) {
//                // syncAlbumPhotos(albumId, false);
//                // }
//            }
//        }
//        if (!didSyncActiveUserName) {
//            ++syncResult.stats.numAuthExceptions;
//        }
        syncUserAlbums(context, context.api.getCurrentUser(), syncResult);
        if (syncAlbumPhotos) {
            syncUserPhotos(context, String.valueOf(context.api.getCurrentUser().uid), syncResult);
        } else {
            // // Always sync added albums.
            // for (Long albumId : context.albumsAdded) {
            // syncAlbumPhotos(albumId, false);
            // }
        }
        context.finish();
    }
    
    private void syncUserAlbums(final SyncContext context, RenrenUserEntry user, final SyncResult syncResult) {
        Log.d(TAG, "syncUserAlbums");
        Log.d(TAG, "RenrenUserEntry: " + user);
        
        final SQLiteDatabase db = context.db;
        Cursor cursor = db.query(RenrenAlbumEntry.SCHEMA.getTableName(), ALBUM_ID_EDITED_PROJECTION, WHERE_ACCOUNT,
                new String[] { String.valueOf(user.uid) }, null, null, null);
        int localCount = cursor.getCount();

        // Build a sorted index with existing entry timestamps.
        final EntryMetadata local[] = new EntryMetadata[localCount];
        for (int i = 0; i != localCount; ++i) {
            cursor.moveToPosition(i); // TODO: throw exception here if returns
                                      // false?
            local[i] = new EntryMetadata(cursor.getLong(0), cursor.getLong(1), 0);
        }
        cursor.close();
        Arrays.sort(local);
        
        for (EntryMetadata metadata: local)
        	Log.d(TAG, "local album: " + String.valueOf(metadata.id));

        // Merge the truth from the API into the local database.
        
        final EntrySchema albumSchema = RenrenAlbumEntry.SCHEMA;
        final EntryMetadata key = new EntryMetadata();
        final AccountManager accountManager = AccountManager.get(getContext());
        context.api.getAlbums(user, new RenrenApi.EntryHandler() {
			@Override
			public void handleEntry(Entry entry) {
				// TODO Auto-generated method stub
				RenrenAlbumEntry album = (RenrenAlbumEntry)entry;
				album.id = album.aid;
				
				Log.d(TAG, "album: " + album.name);
				key.id = album.aid;
				EntryMetadata meta = null;
				for (int i = 0; i < local.length; i++) {
					if (local[i].id == key.id) {
						meta = local[i];
					}
				}
				
				if (meta == null || meta.dateEdited < album.updateTime) {
					albumSchema.insertOrReplace(db, album);
				}
				
				if (meta != null) {
					meta.survived = true;
				}
			}
		});
        Log.d(TAG, "after get albums");
        user.id = user.uid;
        RenrenUserEntry.SCHEMA.insertOrReplace(db, user);

        // Delete all entries not present in the API response.
        for (int i = 0; i != localCount; ++i) {
            EntryMetadata metadata = local[i];
            if (!metadata.survived) {
                deleteAlbum(db, metadata.id);
                ++syncResult.stats.numDeletes;
                Log.i(TAG, "delete album " + metadata.id);
            }
        }

        // Note that albums changed.
        context.albumsChanged = true;
        Log.d(TAG, "after getuser ablums");
    }
    
//    private RenrenAlbumEntry AlbumBeanToEntry(AlbumBean bean) {
//    	RenrenAlbumEntry entry = new RenrenAlbumEntry();
//    	entry.aid = bean.getAid();
//    	entry.uid = bean.getUid();
//    	entry.name = bean.getName();
//    	entry.updateTime = bean.getUpdateTime().toString();
//    	
//    	return entry;
//    }
    
    private void syncUserPhotos(SyncContext context, String account, SyncResult syncResult) {
        // Synchronize albums with out-of-date photos.
    	Log.d(TAG, "syncUserPhotos: " + account);
        SQLiteDatabase db = context.db;
        Cursor cursor = db.query(RenrenAlbumEntry.SCHEMA.getTableName(), Entry.ID_PROJECTION, "uid=?",
                new String[] { account }, null, null, null);
        RenrenAlbumEntry album = new RenrenAlbumEntry();
        Log.d(TAG, "album count: " + cursor.getCount());
        for (int i = 0, count = cursor.getCount(); i != count; ++i) {
            cursor.moveToPosition(i);
            if (RenrenAlbumEntry.SCHEMA.queryWithId(db, cursor.getLong(0), album)) {
                syncAlbumPhotos(context, account, album, syncResult);
            }

            // Abort if interrupted.
            if (Thread.interrupted()) {
                ++syncResult.stats.numIoExceptions;
                Log.e(TAG, "syncUserPhotos interrupted");
            }
        }
        cursor.close();
    }
    
    //同步某相册里的所有photos
    private void syncAlbumPhotos(SyncContext context, final String account, RenrenAlbumEntry album, final SyncResult syncResult) {
        Log.i(TAG, "Syncing Renren album: " + album.name);
        // Query existing album entry (id, dateEdited) sorted by ID.
        final SQLiteDatabase db = context.db;
        long albumId = album.id;
        String[] albumIdArgs = { Long.toString(albumId) };
        Cursor cursor = db.query(RenrenPhotoEntry.SCHEMA.getTableName(), ID_EDITED_INDEX_PROJECTION, WHERE_ALBUM_ID, albumIdArgs, null,
                null, null);
        int localCount = cursor.getCount();

        // Build a sorted index with existing entry timestamps and display
        // indexes.
        final EntryMetadata local[] = new EntryMetadata[localCount];
        final EntryMetadata key = new EntryMetadata();
        for (int i = 0; i != localCount; ++i) {
            cursor.moveToPosition(i); // TODO: throw exception here if returns
                                      // false?
            local[i] = new EntryMetadata(cursor.getLong(0), 0/*cursor.getLong(1)*/, 0/*cursor.getInt(2)*/);
        }
        cursor.close();
        Arrays.sort(local);
        for (EntryMetadata metadata: local)
        	Log.d(TAG, "local photo: " + String.valueOf(metadata.id));

        // Merge the truth from the API into the local database.
        final EntrySchema photoSchema = RenrenPhotoEntry.SCHEMA;
        final int[] displayIndex = { 0 };
        final AccountManager accountManager = AccountManager.get(getContext());
        context.api.getAlbumPhotos(album, new RenrenApi.EntryHandler() {
			
			@Override
			public void handleEntry(Entry entry) {
				// TODO Auto-generated method stub
				RenrenPhotoEntry photo = (RenrenPhotoEntry) entry;
				photo.id = photo.pid;
				Log.d(TAG, "photo url: " + photo.urlTiny);
				key.id = photo.id;
				
				EntryMetadata meta = null;
				for (int i = 0; i < local.length; i++) {
					if (local[i].id == key.id) {
						meta = local[i];
					}
				}
				
				if (meta == null) {
					photoSchema.insertOrReplace(db, photo);
				}
				else {
					meta.survived = true;
				}
			}
		});
        
        int result = 0;
        
        // Return if not modified or on error.
        switch (result) {
        case RenrenApi.RESULT_ERROR:
            ++syncResult.stats.numParseExceptions;
            Log.e(TAG, "syncAlbumPhotos error");
        case RenrenApi.RESULT_NOT_MODIFIED:
            // Log.e(TAG, "result not modified");
            return;
        }

        // Delete all entries not present in the API response.
        for (int i = 0; i != localCount; ++i) {
            EntryMetadata metadata = local[i];
            if (!metadata.survived) {
                deletePhoto(db, metadata.id);
                ++syncResult.stats.numDeletes;
                Log.i(TAG, "delete photo " + metadata.id);
            }
        }

        // Mark album as no longer dirty and store the new ETag.
        album.photosDirty = false;
        RenrenAlbumEntry.SCHEMA.insertOrReplace(db, album);
        // Log.i(TAG, "Clearing dirty bit on album " + albumId);

        // Mark that photos changed.
        // context.photosChanged = true;
        getContext().getContentResolver().notifyChange(ALBUMS_URI, null, false);
        getContext().getContentResolver().notifyChange(PHOTOS_URI, null, false);
    }
    
    private void deletePhoto(SQLiteDatabase db, long photoId) {
    	RenrenPhotoEntry.SCHEMA.deleteWithId(db, photoId);
        deletePhotoCache(photoId);
    }
    
    private void deletePhotoCache(long photoId) {
        // TODO: implement it.
    }
    
    private void deleteAlbum(SQLiteDatabase db, long albumId) {
        // Delete contained photos.
        deleteAlbumPhotos(db, albumId);

        // Delete the album.
        RenrenAlbumEntry.SCHEMA.deleteWithId(db, albumId);
    }
    
    private void deleteAlbumPhotos(SQLiteDatabase db, long albumId) {
        Log.v(TAG, "deleteAlbumPhotos(" + albumId + ")");
        String photoTableName = RenrenPhotoEntry.SCHEMA.getTableName();
        String[] whereArgs = { Long.toString(albumId) };
        Cursor cursor = db.query(photoTableName, Entry.ID_PROJECTION, WHERE_ALBUM_ID, whereArgs, null, null, null);

        // Delete cache entry for each photo.
        if (cursor.moveToFirst()) {
            do {
                deletePhotoCache(cursor.getLong(0));
            } while (cursor.moveToNext());
        }
        cursor.close();

        // Delete all photos.
        db.delete(photoTableName, WHERE_ALBUM_ID, whereArgs);
    }
    
    private final class SyncContext {
        // List of all authenticated user accounts.
        //public PicasaApi.AuthAccount[] accounts;

        // A connection to the Picasa API for a specific user account. Initially
        // null.
        public RenrenApi api = null;//new RenrenApi();

        // A handle to the Picasa databse.
        public SQLiteDatabase db;

        // List of album IDs that were added during the sync.
        public final ArrayList<Long> albumsAdded = new ArrayList<Long>();

        // Set to true if albums were changed.
        public boolean albumsChanged = false;

        // Set to true if photos were changed.
        public boolean photosChanged = false;

        public SyncContext(Context ctx) {
        	api = new RenrenApi(ctx);
            db = mDatabase.getWritableDatabase();
        }

        //public void reloadAccounts() {
            //accounts = PicasaApi.getAuthenticatedAccounts(getContext());
        //}

        public void finish() {
            // Send notifications if needed and reset state.
            ContentResolver cr = getContext().getContentResolver();
            if (albumsChanged) {
                cr.notifyChange(ALBUMS_URI, null, false);
            }
            if (photosChanged) {
                cr.notifyChange(PHOTOS_URI, null, false);
            }
            albumsChanged = false;
            photosChanged = false;
        }

//        public boolean login(String user) {
//            if (accounts == null) {
//                reloadAccounts();
//            }
//            final PicasaApi.AuthAccount[] authAccounts = accounts;
//            for (PicasaApi.AuthAccount auth : authAccounts) {
//                if (auth.user.equals(user)) {
//                    api.setAuth(auth);
//                    return true;
//                }
//            }
//            return false;
//        }
    }
    
    /**
     * Minimal metadata gathered during sync.
     */
    private static final class EntryMetadata implements Comparable<EntryMetadata> {
        public long id;
        public long dateEdited;
        public int displayIndex;
        public boolean survived = false;

        public EntryMetadata() {
        }

        public EntryMetadata(long id, long dateEdited, int displayIndex) {
            this.id = id;
            this.dateEdited = dateEdited;
            this.displayIndex = displayIndex;
        }

        public int compareTo(EntryMetadata other) {
            return Long.signum(id - other.id);
        }

    }
}
